IList

Rapid overview

IList — Indexed Access Collections

"Use IList when you need random access by index, or when Insert/RemoveAt operations are required."

❌ Bad example:

public IEnumerable<Trade> GetRecentTrades()
{
    return _trades.OrderByDescending(t => t.Timestamp).Take(10);
}

// Caller
public void DisplayLastTrade(IEnumerable<Trade> trades)
{
    var lastTrade = trades.Last(); // enumerates entire sequence
    Console.WriteLine(lastTrade.Symbol);
}

Using .Last() on IEnumerable requires full enumeration—inefficient.

✅ Good example:

public IList<Trade> GetRecentTrades()
{
    return _trades.OrderByDescending(t => t.Timestamp).Take(10).ToList();
}

// Caller
public void DisplayLastTrade(IList<Trade> trades)
{
    var lastTrade = trades[trades.Count - 1]; // O(1) indexed access
    Console.WriteLine(lastTrade.Symbol);
}

👉 IList enables O(1) random access via indexer, avoiding enumeration.

🔥 When Insert/RemoveAt matters:

public class OrderBook
{
    public IList<Order> BuyOrders { get; } = new List<Order>();

    public void InsertOrderAtPrice(Order order)
    {
        // Binary search to find insertion point
        int index = BuyOrders.BinarySearch(order, new PriceComparer());
        if (index < 0) index = ~index;

        BuyOrders.Insert(index, order); // insert at specific position
    }
}

👉 IList supports Insert() and RemoveAt() for positional mutations.

🔥 Avoiding unnecessary copies:

// ❌ Bad: forces caller to know concrete type
public List<decimal> CalculatePrices(List<decimal> basePrices)
{
    for (int i = 0; i < basePrices.Count; i++)
        basePrices[i] *= 1.05m;
    return basePrices;
}

// ✅ Good: accepts interface, returns interface
public IList<decimal> CalculatePrices(IList<decimal> basePrices)
{
    for (int i = 0; i < basePrices.Count; i++)
        basePrices[i] *= 1.05m;
    return basePrices;
}

👉 Accepting IList allows arrays or lists without forcing specific types.

💡 In trading systems:

  • Use IList for order books where indexed access is critical for price levels.
  • Enable efficient batch processing with index-based loops (faster than foreach for large arrays).
  • Prefer IEnumerable unless random access is genuinely needed—narrower contract.

---

Questions & Answers

Q: What's the difference between ICollection and IList?

A: IList extends ICollection and adds indexed access (this[int]), Insert(), RemoveAt(), and IndexOf(). Use IList when position matters.

Q: Does IList guarantee O(1) indexed access?

A: Not strictly. List and arrays are O(1), but custom implementations (e.g., linked lists) could be O(n). Documentation should clarify performance.

Q: Should I return IList from methods?

A: Only if callers need indexed access or Insert/RemoveAt. Otherwise, IEnumerable or IReadOnlyList are better—narrower contracts prevent misuse.

Q: Can IList be read-only?

A: Yes. Arrays are fixed-size, so Add/Remove throw NotSupportedException. Check IsReadOnly before assuming mutability. Prefer IReadOnlyList for clarity.

Q: How does IList relate to arrays?

A: Arrays implement IList, providing O(1) indexed access. They're IList but fixed-size, so Add/Remove fail. Use Array.AsReadOnly() for IReadOnlyList.

Q: When should I use for vs foreach with IList?

A: Use for (int i = 0; i < list.Count; i++) when you need the index or modify elements by index. Use foreach for clarity when index isn't needed.

Q: What's the performance of IndexOf() on IList?

A: O(n) for List (linear search). If frequent lookups are needed, use Dictionary or HashSet instead.

Q: Can I pass an array as IList?

A: Yes. Arrays implement IList, but Add/Remove throw exceptions. This enables accepting both arrays and lists without overloads.

Q: How do I mock IList in tests?

A: Use List or arrays directly for simple cases. For complex scenarios, mocking frameworks (Moq, NSubstitute) can stub IList behavior.

Q: What happens if I access IList[index] out of bounds?

A: Throws IndexOutOfRangeException (arrays) or ArgumentOutOfRangeException (List). Always validate index < Count before accessing.